#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         SubversionAccessory.py
#
# Copyright:    Copyright (c) 2006, Faulkner Technologies
#
# Author:       James Davies
#
# Description:  This file defines the SubversionAccessory class,
#               that provides a mechanism to archive an xml formatted
#               model in a subversion repository
#
# See Also:     Accessory, Model
#####################################################################

#####################################################################
## KnowledgeEngine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.Accessory import *
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.WizardStepDefinition import *
from Products.KnowledgeEngine.KERLProcessor import *

#from Products.Undermine.Undermine import getClient

from xml.dom import minidom

#####################################################################
## Zope Library Imports
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from AccessControl.Role import RoleManager
from DateTime import DateTime

from AccessControl import allow_module
#####################################################################
## Python Library Imports
import tempfile
import pwd

try:
    import pysvn
    SVN_IMPORTED = True
except ImportError:
    SVN_IMPORTED = False
    


allow_module('Products.KnowledgeEngine.Accessories.SubversionAccessory')
allow_module('Products.KnowledgeEngine.Accessories.SubversionAccessory.SubversionAccessory')

#####################################################################
## SubversionAccessory Class Definition
class SubversionAccessory(Accessory):
    """
        An interface specifying the standard features of an Accessory
    """

    #---------------------------------------------------------------#

    #define security
    security = ClassSecurityInfo()
    security.declareObjectPublic()
    security.setDefaultAccess("allow")
        
    #---------------------------------------------------------------#

    def getIdentity(self):
        """
            ABSTRACT: Return the unique identity of the accessory
            in the Knowledge Engine
        """
        
        #typically this will be the same as the class name
        return "SubversionAccessory"
        
    #---------------------------------------------------------------#
        
    def getTitle(self, genericObject):
        """
            Returns the title of the Accessory (when
            applied to the specified genericObject)
            
            RETURN TYPE: String
        """

        return "Subversion Tool"
        
    #---------------------------------------------------------------#
        
    def getPurpose(self, genericObject):
        """
            ABSTRACT: Returns the purpose of the Accessory (when
            applied to the specified genericObject)
            
            RETURN TYPE: String
        """

        return "Archives models into a subversion repository"
        
    #---------------------------------------------------------------#
        
    def getIconName(self, genericObject):
        """
            ABSTRACT: Returns the name of the Icon of the Accessory (when
            applied to the specified genericObject).
            
            The IconName represents the image file name that will be found using
            acquisition.
            
            RETURN TYPE: String
        """
        
        return "snapshot.png"
        
    #---------------------------------------------------------------#
        
    def getExecuteActionDisplayName(self, genericObject):
        """
            ABSTRACT: Returns the display name for the execute action of the Accessory (when
            applied to the specified genericObject)
            
            RETURN TYPE: String
        """

        #return different action names based on the locked status of the model
        return "Show"
        
    #---------------------------------------------------------------#
        
    def isAvailableForGenericObject(self, genericObject):
        """
            Returns if the accessory is available for the 
            specified genericObject.
            
            This method is called to determine if an Accessory
            should be displayed for the specified genericObject 
            when rendering it's property editor etc.

            RETURN TYPE: Boolean
        """

        return genericObject.isInstanceOf("Model")
        
    #---------------------------------------------------------------#
        
    def getWizardStepDefinitions(self, context=None):
        """
            ABSTRACT: This method is used by this class to
            determine the actual wizard step definitions
            for the wizard
            
            RETURN: [WizardStepDefinition]
        """

        #a function to determine if an object should be selected initially in a component tree
#         isInitiallySelectedFunction = lambda genericObject: genericObject.isInstanceOf("Component") or genericObject.isInstanceOf("Model")
#                      
#         #a function to determine if an object should be expanded initially in a component tree
#         isInitiallyExpandedFunction = lambda genericObject: genericObject.isInstanceOf("Model")
                     
        #wizardStep1Definition = WizardStepDefinition("Components to include in the generated MindMap", "Choose the components that you would like to appear in your MindMap.")
        #wizardStep1Definition.addPropertyDefinition(PropertyDefinition(identity="components", displayName="Components For MindMap", defaultValue=[], description="Please select the components to appear in the MindMap.", propertyType=pt_NATIVE, propertyStructure=ps_LIST, propertyFormat=pf_GENERIC_OBJECT_CHOOSER, isInitiallySelectedFunction=isInitiallySelectedFunction, isInitiallyExpandedFunction=isInitiallyExpandedFunction, containerSelectionSelectsAll=true))

        #wizardStep2Definition = WizardStepDefinition("Export Options", "Set the following options for your MindManager(tm) output.")
        #wizardStep2Definition.addPropertyDefinition(PropertyDefinition(identity="outputIdentities", displayName="Output Component Unique Identities", defaultValue=false, description="Should component identities be output with the component titles?", propertyType=pt_BOOLEAN, propertyFormat=pf_CHECKBOX))

        #return [wizardStep1Definition, wizardStep2Definition]
        
        return []
        

    #---------------------------------------------------------------#
    
       
        
    def execute(self, genericObject, wizardState, request): 
        """
            Executes the accessory for the specified
            genericObject using the specified wizardState.
            
            OVERRIDE: This method if you require control of the
            returned HTTP response (via the request)
        """            
        
       
        return genericObject.Model_subversion_status()
        
    #---------------------------------------------------------------#

    def getWizardStepFinishButtonTitle(self, wizardState, context=None):
        """
            Return the title of the finished button for the
            wizard step using the provided wizard state.
            
            By default returns 'Finish...'.  Override this
            method if you'd like something else.

            RETURN: String
        """            
        
        return "Execute"
        
    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation

modelInfo = getClassInfo('Model')

if SVN_IMPORTED:
    
    modelInfo.addPropertyDefinition(PropertyDefinition(
        identity="svn_uri", 
        displayName="Subversion URI", 
        description="Enter the subversion folder you wish to save this model into", 
        propertyType=pt_STRING, 
        propertyFormat=pf_TEXT_SINGLELINE, 
    ))
    
    modelInfo.addPropertyDefinition(PropertyDefinition(
        identity="svn_username", 
        displayName="Subversion Username", 
        description="Enter your subversion username", 
        propertyType=pt_STRING, 
        propertyFormat=pf_TEXT_SINGLELINE, 
    ))
    
    modelInfo.addPropertyDefinition(PropertyDefinition(
        identity="svn_password", 
        displayName="Subversion Password", 
        description="Enter your subversion password", 
        propertyType=pt_STRING, 
        propertyFormat=pf_TEXT_SINGLELINE, 
    ))


from Products.KnowledgeEngine.Model.Model import Model

# ----------------------------------------------------------------------

Model.getSvnURI      = lambda self:getattr(self, 'svn_uri', '')
Model.getSvnUsername = lambda self:getattr(self, 'svn_username', '')
Model.getSvnPassword = lambda self:getattr(self, 'svn_password', '')

# ----------------------------------------------------------------------

def getClient(username=None, password=None):
    """ Returns a SVN Client object """
        
    home_dir = pwd.getpwuid(os.getuid())[5]
    client = pysvn.Client(home_dir + "/.subversion")
    
    myusername = username
    def login_details( realm, username, may_save, login_tried=[] ):
        
        if (not myusername) or login_tried:
            return (False, "", "", False)
        
        login_tried.append( True )
        return (True, myusername, password, True)
    
    client.callback_get_login = login_details
    
    return client

# ----------------------------------------------------------------------

def getRepository( model ):
    """ Returns a two-tuple containing a client object, and the temporary checkout location """
    
    svn_uri      = model.getSvnURI()
    svn_username = model.getSvnUsername()
    svn_password = model.getSvnPassword()
    
    if not svn_uri:
        return (None,None)
    
    client = getClient( svn_username, svn_password )
    
    if hasattr( model, '_v_svn_temp' ):
        tmp_path = model._v_svn_temp
        client.update( tmp_path )
    else:
        model._v_svn_temp = tmp_path = tempfile.mkdtemp(prefix='kesvn-')
        client.checkout( svn_uri, tmp_path )
    
    return ( client, tmp_path )

# ----------------------------------------------------------------------

def Model_subversion_snapshot(self, message):
        """ Takes a snapshot of the model and stores in the repository """

        client, tmp_path = getRepository( model=self )
        if not client: 
            return self.Model_subversion_failure()
                   
        myname = self.getIdentity() + ".xml"        
        repo_files = os.listdir( tmp_path )
        
        if myname in repo_files:
            myfile = open( tmp_path + "/" + myname, 'w' )
        else:            
            # If my xml file doesn't exist, create a blank file and add it to the repository
            myfile = open( tmp_path + "/" + myname, 'w' )
            client.add( tmp_path + "/" + myname )
          
            
        xml = self.exportXML()
        
        print >> myfile, xml
        
        myfile.close()
        client.checkin( tmp_path, log_message=(message or '(No Message)'))
        
        return self.Model_subversion_status()
        
    
Model.Model_subversion_snapshot = Model_subversion_snapshot

# ----------------------------------------------------------------------

def Model_subversion_download( self, revision, REQUEST=None ):
    """ Downloads the XML Version of this model from the specified revision """
    
    assert type(revision) is int, "Revision is not int"
    
    client, tmp_path = getRepository( model=self )
    if not client:
        return self.Model_subversion_failure()
    
    client.update( tmp_path, revision=pysvn.Revision( pysvn.opt_revision_kind.number, revision ) )
    
    myname = self.getIdentity() + ".xml"
    
    file = open( tmp_path + "/" + myname, 'r' )
    xml = file.read()
    file.close()
    
    client.update( tmp_path )
    
    if REQUEST:
        
        filename = self.getIdentity() + "-" + str(revision) + ".xml"
        
        REQUEST.RESPONSE.setHeader( 'content-type', 'text/xml' )
        REQUEST.RESPONSE.setHeader( 'content-disposition', 'attachment; filename=%s' % filename )
    
    
    return xml
    
Model.Model_subversion_download = Model_subversion_download   

# ----------------------------------------------------------------------

def Model_subversion_restore( self, revision, REQUEST=None ):
    """ Downloads the XML Version of this model from the specified revision """
    
    assert type(revision) is int, "Revision is not int"
    
    # Get a connection to the repository
    client, tmp_path = getRepository( model=self )
    if not client:
        return self.Model_subversion_failure()
    
    # Update the repository to our specific revision
    client.update( tmp_path, revision=pysvn.Revision( pysvn.opt_revision_kind.number, revision ) )
    
    myname = self.getIdentity() + ".xml"
    
    # Get the XML Data
    file = open( tmp_path + "/" + myname, 'r' )
    xml = file.read()
    file.close()
           
    folder   = self.aq_parent # The folder our model is in 
    model_id = self.getId()
    
    # Delete ourselves. Due to the transactional nature of Zope, this wont complete until the end
    # of the request, so we can still do our thing
    folder.manage_delObjects( ids=[self.getId()] )
    
    folder.knowledgeengine_tool.importObjectFromXML( folder, xml )
    
    model = getattr(folder, model_id)
    
    REQUEST.set( 'portal_status_message', "Model revision %d restored" % revision )
    return model.Model_subversion_status()


def subversion_model_list( context, svn_uri, svn_username, svn_password ):
    """ Lists the availabe models within a subversion path """
    
    # Get a connection to the repository
    client = getClient( svn_username, svn_password )
    
    tmp_path = tempfile.mkdtemp(prefix='kesvn-')
    client.checkout( svn_uri, tmp_path )
    
    # Get a list of all xml files (Which should be model exports )
    xmlfiles = [ file for file in os.listdir( tmp_path ) if file.endswith('.xml') ]
    
    # Clean up
    #os.rmdir( tmp_path )
    
    return xmlfiles, tmp_path
   
def subversion_model_import( context, svn_uri, svn_username, svn_password, modelIdentity, new_identities=False ):
    """ Lists the availabe models within a subversion path """
    
    # Get a connection to the repository
    client = getClient( svn_username, svn_password )
    
    tmp_path = tempfile.mkdtemp(prefix='kesvn-')
    client.checkout( svn_uri, tmp_path )
    
    filename = tmp_path + "/" + modelIdentity + ".xml"
    
    file = open( filename, 'r' )
    xml = file.read()
    file.close()
    
    context.knowledgeengine_tool.importObjectFromXML( context, xml, recalculateIdentities=new_identities )
    
    
    
    
   
    
Model.Model_subversion_restore = Model_subversion_restore

# ----------------------------------------------------------------------
        
def Model_subversion_log(self):        
    """ Returns the subversion log for this model """
    
    client, tmp_path = getRepository( model=self )
    if not client:
        return []
        
    myname = self.getIdentity() + ".xml"        
    repo_files = os.listdir( tmp_path )
    
    log = []
    if myname in repo_files:            
        for item in client.log( tmp_path + "/" + myname ):
            brain = dict( date     = DateTime( item.date ), 
                        author   = item.author,
                        message  = item.message,
                        revision = item.revision.number, )
                        
            log.append( brain )
    
    return log


Model.Model_subversion_log = Model_subversion_log

# ----------------------------------------------------------------------

def getXMLExportFileTitle( filename ):
    """ Reads the specified XML Export file and returns the title of the exported object """
    
    xmlfile = open(filename)
    xmldata = xmlfile.read()
    xmlfile.close()
    
    dom = minidom.parseString( xmldata )
    
    return dom.childNodes[0].getAttribute('title')


if SVN_IMPORTED:
    # Register Accessory Information with the Knowledge Engine
    registerAccessory(SubversionAccessory())
    

#####################################################################
